package escalonamento;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import model.Processo;

/**
 *
 * @author João Gonçalves
 * @author Everton Silva
 */
public class EscalonamentoSRT {

    // Tempo decorrido
    int tempoDecorrido;
    // Tempo médio de execução
    double tempoMedioExecucao;
    // Tempo médio de espera de um processo
    double tempoMedioEspera;
    // Quantidade de processos
    int quantProcessos;
    // Troca de contexto
    int trocaContexto;
    // Processo em execução
    Processo processoExecucao;
    //Lista com os processos em execução e o com os dados recolhidos, respectivamente.
    LinkedList<Processo> listaProcesso;
    Queue<Processo> listaProcessoDados;
    // Construção da String com os dados do processamento
    StringBuilder dadosProcessamento;

    /**
     * Se nenhum processo estiver em execução, irá ser colocado o topo da fila
     * de processos em estado ativo, ou seja, o processo em execução no momento.
     */
    private void novoProcessoExecucao() {

        if ((!listaProcesso.isEmpty()) && (listaProcesso.peek().getStatus() == 0)) {
            processoExecucao = listaProcesso.peek();
            processoExecucao.setStatus(1);
            trocaContexto++; // Nova troca de contexto
        }
    }

    /**
     * Método responsável pelo escalonamento e pela impressão dos dados sobre o
     * mesmo.
     *
     * @param processos Lista de processos
     */
    public String escalonamento(ArrayList<Processo> processos) {

        /*
         * Inicialização das variaveis.
         */
        tempoDecorrido = 0;
        tempoMedioExecucao = 0;
        tempoMedioEspera = 0;
        quantProcessos = processos.size();
        trocaContexto = 0;
        processoExecucao = null;
        listaProcesso = new LinkedList<>();
        listaProcessoDados = new LinkedList<>();

        dadosProcessamento = new StringBuilder("SRT: \ntempo ");

        /*
         * Enumera os processos de P1 a PN, onde N
         * é o número total de processos.
         */
        for (int i = 1; i <= quantProcessos; i++) {
            dadosProcessamento.append(String.format("P%2s%2s", String.valueOf(i), ""));
        }
        // Quebra de linha
        dadosProcessamento.append("\n");

        while (listaProcessoDados.size() != processos.size()) {

            /*
             * Se o processo entra no instante X, então
             * ele será adicionado a fila de processos
             * para execução
             */
            for (Processo processo : processos) {
                if (processo.getDataCriacao() == tempoDecorrido) {
                    /**
                     * Decide o local que vai inserir na lista.
                     */
                    if (listaProcesso.size() == 0) {
                        listaProcesso.add(processo);
                    } else {
                        for (int i = 0; i < listaProcesso.size(); i++) {
                            /*
                             * Se ele tiver uma prioridade maior será inserido na posição
                             * atual do processo da lista em execução. E o resto deslocado
                             * a direta.
                             */
                            if (processo.getDuracao() < listaProcesso.get(i).getDuracaoRestante()) {
                                /*
                                 * Evita que uma tarefa que não esteja no topo, fique marcada como
                                 * tarefa em execução.
                                 */
                                if (listaProcesso.get(i).getStatus() == 1) {
                                    listaProcesso.peek().setStatus(0);
                                }
                                listaProcesso.add(i, processo);
                                break;
                            } else if (i == (listaProcesso.size() - 1)) {
                                listaProcesso.addLast(processo);
                                break;
                            }
                        }
                    }
                }
            }

            /*
             * Defini o processo em execução, se nenhum tiver ativo.
             */
            novoProcessoExecucao();

            /*
             * Contabiliza o tempo
             */
            if (processoExecucao != null) {
                processoExecucao.tempo();
            }

            dadosProcessamento.append(String.format("%2s-%2s ",
                    String.valueOf(tempoDecorrido), (tempoDecorrido + 1)));

            /*
             * Imprime o estado do processo, de acordo com,
             * se ele se encontra em execução (ativo ou espera)
             * ou não está em execução.
             */

            for (int i = 0; i < quantProcessos; i++) {

                if (!listaProcesso.contains(processos.get(i))) {
                    dadosProcessamento.append(String.format("%2s%3s", " ", ""));
                } else {

                    if (processos.get(i).equals(processoExecucao)) {
                        dadosProcessamento.append(String.format("%2s%3s", "#", ""));
                    } else {
                        dadosProcessamento.append(String.format("%2s%3s", "-", ""));
                    }
                }
            }
            // Quebra de linha
            dadosProcessamento.append("\n");

            /*
             * Verifica se o processo já teve sua execução finalizada e então o
             * para, também marcando o seu termino.
             */
            if (processoExecucao != null && processoExecucao.getDuracaoRestante() == 0) {
                processoExecucao = null;
                listaProcesso.peek().setTermino(tempoDecorrido + 1);
                listaProcessoDados.add(listaProcesso.poll());
            }
            // Contabiliza o tempo decorrido de execução
            tempoDecorrido += 1;
        }

        /*
         * Calcula o somatório dos tempos gastos na
         * execução e espera pelos processos.
         */
        for (Processo processo : listaProcessoDados) {
            tempoMedioExecucao += processo.getTermino() - processo.getDataCriacao();
            tempoMedioEspera += processo.getTermino() - processo.getDuracao() - processo.getDataCriacao();
        }

        /*
         * Calcula os tempos médios de execução e espera de acordo
         * com a quantidade de processos.
         */
        if (listaProcessoDados.size() > 0) {
            tempoMedioExecucao = tempoMedioExecucao / quantProcessos;
            tempoMedioEspera = tempoMedioEspera / quantProcessos;

            dadosProcessamento.append(String.format("\nTempo médio execução: %.2fs", tempoMedioExecucao));
            dadosProcessamento.append(String.format("\nTempo médio de espera: %.2fs", tempoMedioEspera));
            dadosProcessamento.append(String.format("\nNúmero de troca de contexto: %d", trocaContexto));
        }

        return dadosProcessamento.toString();
    }
}
